var kurento = require("kurento-client");
var path = require("path");
var fs = require("fs");
var shelljs = require('shelljs');


class TKmsBridge {
    constructor() {
        this.config = {
            clientId: ['c', (new Date()).getTime(), parseInt(100 * Math.random())].join(""),
            version: "kms.v21",
            recorderPath: "/data/rec",
            kmsUrl: "wss://wj.hhdata.cn:10104/kurento",
            clientKmsUrl: "wss://wj.hhdata.cn:10104/kurento",
            peerOptions: {
                configuration: {
                    "iceServers": [{
                            url: "stun:39.171.53.53:3478"
                        },
                        {
                            urls: ["turn:39.171.53.53:3478"],
                            username: "kurento",
                            credential: "kurento"
                        }
                    ]
                },
                mediaConstraints: {
                    audio: true,
                    video: {
                        width: { min: 1280, ideal: 1920, max: 1920 },
                        height: { min: 720, ideal: 1080, max: 1080 }
                    },
                }
            }
        };
        this.kmsObjs = {};
        this.kmsTags = {};
        this.kmsClient = null;
    };
    freeObjs(objs) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var index = 0;
                var objIds = [];
                var free = () => {
                    if (index < objs.length) {
                        objIds.push(objs[index].id);
                        objs[index].release().then(_ => {
                            index++;
                            free();
                        })
                    } else {
                        success();
                    }
                };
                free();
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    clearObjs() {
        var me = this;
        var objs = [];
        return new Promise((success, faild) => {
            try {
                var getObjs = async() => {
                    await me.getClient();
                    var ps = await me.kmsClient.svr.getPipelines();
                    for (var i = 0; i < ps.length; i++) {
                        var items = await ps[i].getChildren();
                        for (var k = 0; k < items.length; k++) {
                            objs.push(items[k]);
                        }
                        objs.push(ps[i]);
                    }
                };

                getObjs().then(_ => {
                    return me.freeObjs();
                }).then(_ => {
                    success();
                })
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    registKmsObj(obj, code, clientId, info) {
        return new Promise((success, faild) => {
            var me = this;
            try {
                var tag = TKmsTag.getKmsTag(obj, code, clientId, info);
                me.getClient().then(_ => {
                    var tagKey = [me.config.version, obj.id].join("_");
                    tag.tagKey = tagKey;
                    return me.kmsClient.svr.addTag(tagKey, JSON.stringify(tag));
                }).then(_ => {
                    me.kmsTags[obj.id] = tag;
                    obj.tag = tag;
                    me.kmsObjs[obj.id] = obj;
                    success(obj);
                });
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    unRegistKmsObj(objId) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                if (me.kmsTags[objId] && me.kmsTags[objId] != null) {
                    var tagKey = me.kmsTags[objId].tagKey;
                    me.kmsClient.svr.removeTag(tagKey).then(_ => {
                        delete me.kmsTags[objId];
                        success();
                    });
                } else {
                    success();
                }
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    getClient() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var onGetClient = (client) => {
                    if (client.svr && client.svr != null) {
                        success(client);
                    } else {
                        client.getServerManager().then(_svr => {
                            client.svr = _svr;
                            _svr.on('ObjectCreated', function(event) {
                                console.log('create kms obj:', event.object);
                            });
                            _svr.on('ObjectDestroyed', function(event) {
                                try {
                                    var id = event.objectId;
                                    me.unRegistKmsObj(id).then(_ => {
                                        success('free tag ...');
                                    })
                                } catch (eer) {
                                    console.log(eer);
                                }
                            });
                            return me.syncObjTags();
                        }).then(_ => {
                            success(me.kmsClient);
                        });
                    }
                };
                if (me.kmsClient && me.kmsClient != null) {
                    onGetClient(me.kmsClient);
                } else {
                    kurento(me.config.kmsUrl, function(erClient, client) {
                        if (erClient && erClient != null) {
                            success(null);
                        } else {
                            me.kmsClient = client;
                            onGetClient(client);
                        }
                    });
                }
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    syncObjTags() {
        var me = this;
        return new Promise((success, faild) => {
            try {
                var getTags = () => {
                    return new Promise((su, fa) => {
                        try {
                            me.getClient().then(client => {
                                return client.svr.getTags();
                            }).then(_tags => {
                                _tags.forEach(t => {
                                    var flag = t.key.indexOf(me.config.version) == 0;
                                    if (flag) {
                                        var info = JSON.parse(t.value);
                                        tags[info.kmsId] = info;
                                    }
                                });
                                su();
                            });
                        } catch (err) {
                            su();
                        }
                    });
                };
                var getTagPipes = () => {
                    return new Promise((su, fa) => {
                        try {
                            me.kmsClient.svr.getPipelines().then(_ps => {
                                pipes = _ps.filter(p => {
                                    var flag = tags[p.id] && tags[p.id] != null;
                                    if (flag) {
                                        p.tag = tags[p.id];
                                    }
                                    return flag;
                                });
                                su();
                            });
                        } catch (err) {
                            su();
                        }
                    });
                };
                var getPipeObjs = () => {
                    return new Promise((su, fa) => {
                        try {
                            var index = 0;
                            var loadItem = () => {
                                if (index < pipes.length) {
                                    var pipe = pipes[index];
                                    objs[pipe.id] = pipe;
                                    pipe.getChildren().then(_items => {
                                        _items.forEach(itemObj => {
                                            if (tags[itemObj.id] && tags[itemObj.id] != null) {
                                                itemObj.tag = tags[itemObj.id];
                                                objs[itemObj.id] = itemObj;
                                            }
                                        });
                                        su();
                                    });
                                } else {
                                    su();
                                }
                            };
                            loadItem();
                        } catch (err) {
                            su();
                        }
                    });
                };
                var getTimeoutTags = () => {
                    return new Promise((su, fa) => {
                        try {
                            timeoutTags = Object.value(tags).filter(t => {
                                var tagObj = objs[t.kmsId];
                                var flag = !(tagObj && tagObj != null);
                                return flag;
                            });
                            su();
                        } catch (err) {
                            su();
                        }
                    });
                };
                var removeTimeoutTags = () => {
                    return new Promise((su, fa) => {
                        try {
                            var freeItem = () => {
                                if (timeoutTags.length > 0) {
                                    var tag = timeoutTags.shift();
                                    me.kmsClient.svr.removeTag(tag.kmsId).then(_ => {
                                        delete tags[tag.kmsId];
                                        freeItem();
                                    })
                                } else {
                                    su();
                                }
                            };
                            freeItem();
                        } catch (err) {
                            su();
                        }
                    });
                };
                var objs = {};
                var tags = {};
                var pipes = {};
                var timeoutTags = [];
                getTags().then(_ => {
                    return getTagPipes();
                }).then(_ => {
                    return getPipeObjs();
                }).then(_ => {
                    return getTimeoutTags();
                }).then(_ => {
                    return removeTimeoutTags();
                }).then(_ => {
                    me.kmsTags = tags;
                    me.kmsObjs = objs;
                    success();
                });
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    checkKmsObjById(objId) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                me.getClient().then(client => {
                    client.getMediaobjectById(objId, function(erObj, _obj) {
                        if (_obj && _obj != null) {
                            if (me.kmsTags[_obj.id]) {
                                _obj.tag = me.kmsTags[_obj.id];
                            }
                            success(_obj);
                        } else {
                            if (me.kmsObjs[objId] && me.kmsObjs[objId] != null) {
                                delete me.kmsObjs[objId];
                            }
                            if (me.kmsTags[objId] && me.kmsTags[objId] != null) {
                                var tagKey = me.kmsTags[objId].tagKey;
                                me.kmsClient.svr.removeTag(tagKey).then(_ => {
                                    delete me.kmsTags[objId];
                                    success();
                                })
                            } else {
                                success();
                            }
                        }
                    });
                });
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    checkKmsObj(obj) {
        var me = this;
        return new Promise((success, faild) => {
            try {
                if (obj && obj != null) {
                    var objId = obj.id;
                    me.checkKmsObjById(objId).then(res => {
                        success(res);
                    })
                } else {
                    success(null);
                }
            } catch (er) {
                console.log(er);
                success(null);
            }
        })
    };
    getChannel(channelCode) {
        var me = this;
        var res = {
            channel: null,
            seats: {}
        }
        return new Promise((success, faild) => {
            try {
                var chTag = Object.values(me.kmsTags).find(t => {
                    var flag = t.kmsType == "MediaPipeline" && t.code == channelCode;
                    return flag;
                });
                if (chTag && chTag != null && chTag.kmsId && chTag.kmsId.length > 0) {
                    me.getClient().then(_ => {
                        return me.checkKmsObjById(chTag.kmsId);
                    }).then(ch => {
                        res.channel = ch;
                        if (ch && ch != null) {
                            ch.getChildren().then(_items => {
                                var loadSeats = () => {
                                    if (_items.length > 0) {
                                        var item = _items.shift();
                                        var tag = me.kmsTags[item.id];
                                        if (tag && tag != null && tag.kmsType == "PassThrough") {
                                            item.tag = tag;
                                            res.seats[tag.code] = item;
                                        }
                                        loadSeats();
                                    } else {
                                        success(res);
                                    }
                                };
                                loadSeats();
                            });
                        } else {
                            success(res);
                        }
                    });
                } else {
                    success(res);
                }
            } catch (er) {
                console.log(er);
                success(res);
            }
        })
    };
    setChannel(channelCode, seatCodes) {
        var me = this;
        var res = {
            channel: null,
            seats: {}
        }
        return new Promise((success, faild) => {
            try {
                var loadChannel = () => {
                    return new Promise((su, fa) => {
                        try {
                            if (res.channel && res.channel != null) {
                                su();
                            } else {
                                me.kmsClient.create("MediaPipeline").then(pipe => {
                                    res.channel = pipe;
                                    var info = { modelType: 'channel', channelCode: channelCode };
                                    return me.registKmsObj(pipe, channelCode, 'server', info)
                                }).then(_ => {
                                    su();
                                })
                            }
                        } catch (err) {
                            su();
                        }
                    });
                };
                var loadSeats = () => {
                    return new Promise((su, fa) => {
                        try {
                            var codes = seatCodes.filter(s => {
                                return !(res.seats[s] && res.seats[s] != null);
                            });
                            var loadItem = () => {
                                if (codes.length > 0) {
                                    var code = codes.shift();
                                    res.channel.create("PassThrough").then(pt => {
                                        res.seats[code] = pt;
                                        var info = {
                                            modelType: "seat",
                                            channelCode: channelCode,
                                            seatCode: code
                                        }
                                        return me.registKmsObj(pt, code, 'server', info);
                                    }).then(_ => {
                                        loadItem();
                                    });
                                } else {
                                    su();
                                }
                            };
                            loadItem();
                        } catch (err) {
                            su();
                        }
                    });
                };
                me.getChannel(channelCode).then(_res => {
                    res = _res;
                    loadChannel().then(_ => {
                        return loadSeats();
                    }).then(_ => {
                        success(res);
                    })
                })
            } catch (er) {
                console.log(er);
                success(res);
            }
        })
    };
    getChannelData(ch) {
        var me = this;
        var res = {
            channel: null,
            seats: {}
        }
        try {
            res.channel = ch.channel.tag;
            Object.values(ch.seats).forEach(s => {
                res.seats[s.tag.code] = s.tag;
            });
        } catch (er) {
            console.log(er);
        }
        return res;
    };
    connectMedia(srcId, tarId, mediaType) {
        var me = this;
        var res = {
            srcFlag: false,
            tarFlag: false,
            connectFlag: false
        }
        return new Promise((success, faild) => {
            try {
                var checkObj = (objId) => {
                    return new Promise((su, fa) => {
                        try {
                            me.getClient().then(client => {
                                client.getMediaobjectById(objId, function(err, obj) {
                                    su(obj && obj != null ? obj : null);
                                });
                            });
                        } catch (err) {
                            su();
                        }
                    });
                };
                var src = null;
                var tar = null;
                checkObj(srcId).then(_src => {
                    src = _src;
                    res.srcFlag = src != null;
                    return checkObj(tarId);
                }).then(_tar => {
                    tar = _tar;
                    res.tarFlag = tar != null;
                    if (res.srcFlag && res.tarFlag) {
                        if (mediaType == "AUDIO" || mediaType == "VIDEO") {
                            src.connect(tar, mediaType, function(eer) {
                                res.connectFlag = !(eer && eer != null);
                                success(res);
                            })
                        } else {
                            src.connect(tar, function(eer) {
                                res.connectFlag = !(eer && eer != null);
                                success(res);
                            })
                        }
                    } else {
                        success(res);
                    }
                });
            } catch (er) {
                console.log(er);
                success(res);
            }
        })
    };
    startRecord(srcId, recFn) {
        var me = this;
        var res = {
            srcFlag: false,
            recorderId: "",
            recordFlag: false,
            recFn: recFn
        }
        return new Promise((success, faild) => {
            try {
                var fullFn = path.join(a.config.recorderPath, recFn);
                if (fullFn.indexOf(".webm") < 0) {
                    fullFn += ".webm";
                }
                var fnDir = path.parse(fullFn).dir;
                if (!fs.existsSync(fnDir)) {
                    shelljs.mkdir('-p', fnDir);
                    if (fs.existsSync(fnDir)) {
                        shelljs.chmod(777, fnDir);
                    }
                }
                me.getClient().then(client => {
                    return client.getMediaobjectById(srcId).then(src => {
                        if (src && src != null) {
                            srcFlag = true;
                            var pipeId = (srcId.split("/"))[0];
                            client.getMediaobjectById(pipeId).then(pipe => {
                                var opts = {
                                    stopOnEndOfStream: true,
                                    uri: 'file://' + fullFn
                                };
                                var recorder = null;
                                pipe.create("RecorderEndpoint", opts).then(_recorder => {
                                    recorder = _recorder;
                                    res.recorderId = recorder.id;
                                    recorder.on("Stopped", function(evData) {
                                        recorder.release();
                                    });
                                    var info = {
                                        modelType: "recorder",
                                        srcId: srcId,
                                        recFn: recFn,
                                        startTm: (new Date()).getTime()
                                    };
                                    return me.registKmsObj(recorder, recFn, "server", info);
                                }).then(_ => {
                                    return src.connect(recorder);
                                }).then(_ => {
                                    return recorder.record();
                                }).then(_ => {
                                    res.recordFlag = true;
                                    success(res);
                                });
                            });
                        } else {
                            success(res);
                        }
                    });
                });
            } catch (er) {
                console.log(er);
                success(res);
            }
        })
    };
    stopRecord(recorderId) {
        var me = this;
        var res = {
            srcFlag: false,
            recorderId: "",
            recordFlag: false,
            recFn: recFn
        }
        return new Promise((success, faild) => {
            try {
                me.getClient().then(client => {
                    return client.getMediaobjectById(recorderId).then(recorder => {
                        if (recorder && recorder != null) {
                            recorder.stopAndWait().then(_ => {
                                success();
                            });
                        } else {
                            success();
                        }
                    });
                });
            } catch (er) {
                console.log(er);
                success();
            }
        })
    };
    createEnd(pipeId, endCode, clientId, channelCode) {
        var me = this;
        var res = {
            end: null,
            errorCode: -1
        };
        return new Promise((success, faild) => {
            try {
                me.getClient().then(client => {
                    return client.getMediaobjectById(pipeId).then(pipe => {
                        if (pipe && pipe != null) {
                            pipe.create("WebRtcEndpoint").then(end => {
                                res.end = end;
                                var info = {
                                    modelType: "end",
                                    endCode: endCode,
                                    channelCode: channelCode,
                                    clientId: clientId
                                };
                                return me.registKmsObj(end, endCode, clientId, info);
                            }).then(_ => {
                                success(res);
                            });
                        } else {
                            success(res);
                        }
                    });
                });
            } catch (er) {
                console.log(er);
                success(res);
            }
        })
    };


    //----------------------------------------------------

    regist2WebSvr(webSvr) {
        var me = this;
        if (webSvr && webSvr != null && webSvr.registRoute) {
            var respJson = function(result, response) {
                try {
                    response.send(JSON.stringify(result));
                } catch (eer) {
                    console.log(eer);
                }
            };
            webSvr.registRoute("kms", "bridge", "getSvrTime", function(result, request, response) {
                try {
                    result.data = (new Date()).getTime();
                    respJson(result, response);
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "getConfig", function(result, request, response) {
                try {
                    result.data = {
                        kmsUrl: me.config.clientKmsUrl,
                        version: me.config.version,
                        peerOptions: me.config.peerOptions,
                        svrTime: (new Date()).getTime()
                    };
                    respJson(result, response);
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "clearObjs", function(result, request, response) {
                try {
                    me.clearObjs().then(_ => {
                        result.data = 1;
                        respJson(result, response);
                    });
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "getChannel", function(result, request, response) {
                try {
                    var channelCode = result.ps.channelCode || "";
                    if (channelCode == "") {
                        result.data = null;
                        result.error = "channelCode is null.";
                        respJson(result, response);
                    } else {
                        me.getChannel(channelCode).then(_res => {
                            result.data = me.getChannelData(_res);
                            respJson(result, response);
                        })
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "setChannel", function(result, request, response) {
                try {
                    var channelCode = result.ps.channelCode || "";
                    var seatCodes = result.ps.seatCodes.split(",");
                    if (channelCode == "") {
                        result.data = null;
                        result.error = "channelCode is null.";
                        respJson(result, response);
                    } else {
                        me.setChannel(channelCode, seatCodes).then(_res => {
                            result.data = me.getChannelData(_res);
                            respJson(result, response);
                        })
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "connectMedia", function(result, request, response) {
                try {
                    var srcId = result.ps.srcId || "";
                    var tarId = result.ps.tarId || "";
                    var mediaType = result.ps.mediaType || "";
                    me.connectMedia(srcId, tarId, mediaType).then(_res => {
                        result.data = _res;
                        respJson(result, response);
                    });
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "startRecord", function(result, request, response) {
                try {
                    var srcId = result.ps.srcId || "";
                    var recFn = result.ps.recFn || "";
                    if (recFn == "") {
                        result.data = null;
                        result.error = "recFn is null.";
                        respJson(result, response);
                    } else {
                        me.startRecord(srcId, recFn).then(_res => {
                            result.data = _res;
                            respJson(result, response);
                        })
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "stopRecord", function(result, request, response) {
                try {
                    var recorderId = result.ps.recorderId || "";
                    if (recorderId == "") {
                        result.data = null;
                        result.error = "recorderId is null.";
                        respJson(result, response);
                    } else {
                        me.stopRecord(recorderId).then(_res => {
                            result.data = _res;
                            respJson(result, response);
                        })
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
            webSvr.registRoute("kms", "bridge", "createEnd", function(result, request, response) {
                try {
                    var endCode = result.ps.endCode || "";
                    var channelKmsId = result.ps.channelKmsId || "";
                    var clientId = result.ps.clientId || "";
                    var channelCode = result.ps.channelCode || "";
                    if (endCode == "") {
                        result.data = null;
                        result.error = "recorderId is null.";
                        respJson(result, response);
                    } else if (channelKmsId == "" || !(me.kmsObjs[channelKmsId]) || me.kmsObjs[channelKmsId] == null) {
                        result.data = null;
                        result.error = "channelKmsId is null.";
                        respJson(result, response);
                    } else {
                        me.createEnd(channelKmsId, endCode, clientId, channelCode).then(_res => {
                            result.data = _res.end.tag;
                            respJson(result, response);
                        });
                    }
                } catch (err) {
                    console.log(err);
                    respJson(result, response);
                }
            });
        }
    }
}

class TKmsTag {
    constructor() {
        this.kmsId = "";
        this.kmsType = "";
        this.code = "";
        this.clientId = "";
    };
    static getKmsTag(obj, code, clientId, info) {
        var tag = new TKmsTag();
        try {
            var arr = obj.id.split('.');
            tag.kmsId = obj.id;
            tag.kmsType = arr[arr.length - 1];
            tag.code = code;
            tag.clientId = clientId;
            var tagData = info || {};
            Object.keys(tagData).forEach(k => {
                tag[k] = tagData[k];
            });
        } catch (er) {
            console.log(er);
        }
        return tag;
    }
}

module.exports = {
    TKmsBridge: TKmsBridge
}

/*
var a = new TKmsBridge();
a.getClient().then(_ => {
    var fn = 'info/data/r1.webm';
    fullFn = path.join(a.config.recorderPath, fn);
    var fnDir = path.parse(fullFn).dir;
    console.log(fullFn, fnDir);
})
*/